package com.cjq.config;

import com.cjq.domain.Admin;
import com.cjq.domain.User;
import com.cjq.filter.JwtTokenCheckFilter;
import com.cjq.service.SecurityUserDetailService;
import com.cjq.utils.Constant;
import com.cjq.utils.JwtUtils;
import com.cjq.utils.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.StringUtils;

import java.io.PrintWriter;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

/**
 * @author junqiancai@foxmail.com
 * @since 2021/6/10 10:17
 */
@Configuration
//@EnableWebSecurity(debug = true)
@Slf4j
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {


    //用于jwt验证
    @Autowired
    private JwtTokenCheckFilter jwtTokenCheckFilter;
    //数据库查用户信息
    @Autowired
    private SecurityUserDetailService securityUserDetailService;
    //用于解决jwtToken登出问题
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //自定义认证提供者
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider
                = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(securityUserDetailService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.inMemoryAuthentication()
//                .withUser("qqq").password(passwordEncoder().encode("qqq")).roles("USER")
//                .and()
//                .withUser("bbb").password(passwordEncoder().encode("bbb")).roles("BBB");
        auth.authenticationProvider(authenticationProvider());  //自定义认证方式
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //登录成功和失败都返回界面
//        http.formLogin().successForwardUrl("/welcome").failureForwardUrl("/fail");

        /*前后端分离时需要返回json，需要自定义处理器*/
        http.formLogin()
                .loginProcessingUrl("/user/login")
                .successHandler(authenticationSuccessHandler())
                .failureHandler(authenticationFailureHandler())
                .permitAll();

        //即使自定义了登出路径，原来的默认/logout还是可以访问，只是点击后无效
        //因为JwtCheckFilter不会拦截logoutUrl，故不能用于登出，可在controller中定义登出路径
//        http.logout().logoutUrl("/user/logout").logoutSuccessHandler(logoutSuccessHandler());


        //自定义异常处理器中的权限不足处理器
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());

        /*自定义时，需要屏蔽跨站请求*/
        http.csrf().disable();

        /*放行所有路径，禁用session，使用jwt的过滤器进行验证*/
        http.sessionManagement().disable();
        http.authorizeRequests().antMatchers("/**").permitAll();
//        http.authorizeRequests().anyRequest().permitAll();

        /*添加jwt过滤器，让其在用户名密码校验前解析token，解析成功就设置Authentication信息到上下文*/
        /*UsernamePasswordAuthenticationFilter校验时会先看上下文中有无认证信息，有则默认校验通过*/
        http.addFilterBefore(jwtTokenCheckFilter, UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 前后端分离时需要返回json，需要自定义处理器
     */

    @Bean
    AuthenticationSuccessHandler authenticationSuccessHandler(){
        return (request, response, authentication)->{
            response.setContentType("application/json;charset=utf-8");
            //获取认证成功后的用户信息，因为在UserDetailsService认证成功后会把userDetail赋值到authentication的principal中
            //而在UserDetailsService中使用的是实现了UserDetail接口的User
            String loginType = request.getParameter(Constant.LOGIN_TYPE);
//            List<String> permit = new ArrayList<>();    //需要放入jwt中的角色或权限信息
            HashMap<String, Object> map = new HashMap<>();  //需要放入jwt中的payload
            if(Constant.ADMIN_LOGIN.equals(loginType)){
                //管理员登录
                Admin admin = (Admin)authentication.getPrincipal();
                //认证通过把角色id写入jwt中
//                Integer roleId = admin.getRoleId();
//                List<GrantedAuthority> authorities = new ArrayList<>();
//                authorities.add(new SimpleGrantedAuthority(String.valueOf(roleId)));
//                admin.setAuthorities(authorities);
//                authorities.forEach(auth->permit.add(auth.getAuthority()));
                //把admin的角色或权限信息存入jwt中
                map.put("id",admin.getId());
                map.put("username",admin.getUsername());
                map.put("role",admin.getRoleId());
            }
            else{
                //普通用户登录
                User user = (User)authentication.getPrincipal();
//                Integer roleId = user.getRoleId();
//                List<GrantedAuthority> authorities = new ArrayList<>();
//                //因为是无状态，是不是不需要写入，只需在jwtToken过滤器中写入Authority
//                authorities.add(new SimpleGrantedAuthority(Constant.PERMIT_PREFIXED+roleId));
//                user.setAuthorities(authorities);
//                //把admin的权限或角色信息存入permit用于写入jwt中
//                authorities.forEach(auth->permit.add(auth.getAuthority()));
                //利用user中的信息,用于生成jwtToken
                map.put("id",user.getId());
                map.put("username",user.getUsername());
                map.put("userType",user.getUserType());
                map.put("realName",user.getRealName());
                map.put("role",user.getRoleId());
            }
            String jwtToken = JwtUtils.createJwtToken(map);
            //把token存入redis解决登出问题
            redisTemplate.opsForValue().set(JwtUtils.REDIS_PREFIXED+jwtToken,"", Duration.ofSeconds(JwtUtils.EXPIRE_TIME));
            //把token返回
            ObjectMapper om = new ObjectMapper();
            String result = om.writeValueAsString(Result.succeed(Result.SUCCESS,"认证成功",jwtToken));
            PrintWriter writer = response.getWriter();
            writer.write(result);
            writer.flush();
            writer.close();
        };
    }

    //自定义登录失败处理器
    @Bean
    AuthenticationFailureHandler authenticationFailureHandler(){
        return (request, response,exception)->{
            response.setContentType("application/json;charset=utf-8");
            HashMap<String,Object> map = new HashMap<>();
            ObjectMapper om = new ObjectMapper();
            String result = om.writeValueAsString(Result.succeed(Result.NOT_AUTH,"认证失败"));
            PrintWriter writer = response.getWriter();
            writer.write(result);
            writer.flush();
            writer.close();
        };
    }

    /**
     * http.logout().logoutUrl("/user/logout").logoutSuccessHandler(logoutSuccessHandler());
     * 通过上方配置的logoutUrl不会被JwtTokenCheckFilter拦截
     * 故，不可以通过登出处理器来删除token，可能token不合法
     * @return
     */
    @Bean
    LogoutSuccessHandler logoutSuccessHandler(){
        return (req,res,auth)->{
            //登出时根据请求头中的token删除redis中的key
            String header = req.getHeader(JwtUtils.AUTHENTICATION);
            if(StringUtils.hasText(header)){
                String jwtToken = header.replaceAll("bearer ", "");
                if(StringUtils.hasText(jwtToken)){
                    redisTemplate.delete(JwtUtils.REDIS_PREFIXED+jwtToken);
                    res.setContentType("application/json;charset=utf-8");
                    ObjectMapper om = new ObjectMapper();
                    String result = om.writeValueAsString(Result.succeed(Result.SUCCESS,"登出成功"));
                    res.getWriter().write(result);
                }
            }
        };
    }
    //自定义权限不足处理器
    @Bean
    AccessDeniedHandler accessDeniedHandler(){
        return (request, response,exception)->{
            response.setContentType("application/json;charset=utf-8");
            response.setContentType("application/json;charset=utf-8");
            ObjectMapper om = new ObjectMapper();
            String result = om.writeValueAsString(Result.succeed(Result.SUCCESS,"权限不足"));
            response.getWriter().write(result);
        };
    }
}
